let LINE_AWAY_DISTANCE = 16; // 折线绕开node距离
let POINT_CUT_DISTANCE = 5; // edge中连接点裁剪边距或折线裁剪边距
let LINE_SELECT_OFFSET = 5; // edge 可选偏移距离
let rectMargin = 10;
let TRANSLATE_MATRIX = {
  n: [0, -1],
  e: [1, 0],
  s: [0, 1],
  w: [-1, 0]
};

/**
 * 移动终点坐标
 * @param  {[type]} point           [description]
 * @param  {[type]} translateMatrix [description]
 * @param  {[type]} distance        [description]
 * @return {[type]}                 [description]
 */
function calcMovedPoint(point, translateMatrix, distance) {
  return {
    x: point.x + translateMatrix[0] * distance,
    y: point.y + translateMatrix[1] * distance
  };
}

/**
 * 向量
 * @param  {[type]} src [description]
 * @param  {[type]} dst [description]
 * @return {[type]}     [description]
 */
function calcVector(src, dst) {
  return {
    x: dst.x - src.x,
    y: dst.y - dst.y
  }
}

/**
 * 附加给射线的不等式条件
 * @param  {[type]} base      [description]
 * @param  {[type]} direction [description]
 * @return {[type]}           [description]
 */
function calcInequation(base, direction) {
  let translateMatrix = TRANSLATE_MATRIX[direction];
  return function(point) {
    //向右，x大，向上，y小
    if (translateMatrix[0] * (point.x - base.x) >= 0 && translateMatrix[1] * (point.y - base.y) >= 0) {
      return true;
    } else {
      return false;
    }
  }
}

/**
 * 射线数组表示
 * @return {[type]} [description]
 */
function calcRay(point, direction) {
  if (direction === 's' || direction === 'n') {
    return [1, 0, point.x];
  } else {
    return [0, 1, point.y];
  }
}

/**
 * 根据一个点，方向和距离生成 目的点、向量、射线方程
 * todo: 射线方程
 * @param  {[type]} point     [description]
 * @param  {[type]} direction [description]
 * @param  {[type]} distance  [description]
 * @return {[type]}           [description]
 * {
 *   point 下个点坐标
 *   vector 当前点到下个点的向量
 *   ray 线的表示如 ax+by=c 表示为 [a, b, c]
 * }
 */
function calcNextPoint(point, direction, distance) {
  let dstPoint = calcMovedPoint(point, TRANSLATE_MATRIX[direction], distance);
  return {
    point: dstPoint,
    vector: calcVector(point, dstPoint),
    ray: calcRay(point, direction),
    inequation: calcInequation(point, direction)
  }
}

/**
 * 根据起始方向判断后续弯折的方向
 * @param  {[type]} direction [description]
 * @param  {[type]} src       [description]
 * @param  {[type]} dst       [description]
 * @return {[type]}           [description]
 */
function calcNextDirection(direction, src, dst) {
  if (direction === 'e' || direction === 'w') {
    if (dst.y > src.y) {
      return 's';
    } else {
      return 'n';
    }
  } else {
    if (dst.x > src.x) {
      return 'e';
    } else {
      return 'w';
    }
  }
}

function calcIntersectPoint(srcRay, dstRay) {
  if (srcRay[0] === 1) {
    return { x: srcRay[2], y: dstRay[2] }
  } else {
    return { x: dstRay[2], y: srcRay[2] }
  }
}

/**
 * 相交且满足条件返回交点
 * @param  {[type]} src [description]
 * @param  {[type]} dst [description]
 * @return {[type]}     [description]
 */
function checkIntersect(src, dst) {
  //垂直
  if (src.ray[0] !== dst.ray[0] && src.ray[1] !== dst.ray[1]) {
    let intersectPoint = calcIntersectPoint(src.ray, dst.ray);
    if (src.inequation(intersectPoint) && dst.inequation(intersectPoint)) {
      return intersectPoint;
    }
  }

  return false;
}

/**
 * point 规定格式 {x:0,y:0}
 * direction 规定格式 's/w/e/n'
 * 返回edge的一些option:
 * @param  {object} opts
 * point为连接点在外切矩形上的映射
 * node为外切矩形的style
 * {
 *   src: { point:{x,y}, direction:'', boundry:{x,y,width,height}},
 *   dst: { point:{x,y}, direction:'', boundry:{x,y,width,height}}
 * }
 * @return {object}
 * {
 *   style: {x,y,width,height}
 *   attr: {
 *     point: [[x1,y1],[x2,y2],...]
 *   }
 * }
 */
function calcBrokenLinePath(opts) {
  let source = opts.src;
  let target = opts.dst;

  let sourcePoint = source.point;
  let targetPoint = target.point;

  let sourceBoundry = source.boundry;
  let targetBoundry = target.boundry;

  let testIntersect = null;
  //0
  let srcNext1 = calcNextPoint(sourcePoint, source.direction, LINE_AWAY_DISTANCE)
  let dstNext1 = calcNextPoint(targetPoint, target.direction, LINE_AWAY_DISTANCE)

  //{x:0,y:0}
  testIntersect = checkIntersect(srcNext1, dstNext1)
  if (testIntersect) {
    return combinePoints(sourcePoint, testIntersect, targetPoint);
  }

  let srcDirection1 = calcNextDirection(source.direction, sourceBoundry, targetBoundry);
  let dstDirection1 = calcNextDirection(target.direction, targetBoundry, sourceBoundry);

  let srcNext2 = calcNextPoint(srcNext1.point, srcDirection1, LINE_AWAY_DISTANCE)
  let dstNext2 = calcNextPoint(dstNext1.point, dstDirection1, LINE_AWAY_DISTANCE)

  testIntersect = checkIntersect(srcNext2, dstNext1)
  if (testIntersect) {
    return combinePoints(sourcePoint, srcNext1.point, testIntersect, targetPoint);
  }

  testIntersect = checkIntersect(srcNext1, dstNext2)
  if (testIntersect) {
    return combinePoints(sourcePoint, testIntersect, dstNext1.point, targetPoint);
  }

  testIntersect = checkIntersect(srcNext2, dstNext2)
  if (testIntersect) {
    return combinePoints(sourcePoint, srcNext1.point, testIntersect, dstNext1.point, targetPoint);
  }

  let srcDirection2 = calcNextDirection(srcDirection1, sourceBoundry, targetBoundry);
  let srcNext3 = calcNextPoint(srcNext2.point, srcDirection2, LINE_AWAY_DISTANCE);
  testIntersect = checkIntersect(srcNext3, dstNext2)
  if (testIntersect) {
    return combinePoints(sourcePoint, srcNext1.point, srcNext2.point, testIntersect, dstNext1.point, targetPoint);
  }

  return combinePoints(sourcePoint, targetPoint);
}

function combinePoints() {
  let points = Array.prototype.slice.call(arguments);
  let minPoint = { x: points[0].x, y: points[0].y };
  let maxPoint = { x: points[0].x, y: points[0].y };
  let point = null;

  for (let i = 1; i < points.length; i++) {
    point = points[i];

    if (minPoint.x > point.x) {
      minPoint.x = point.x
    }
    if (minPoint.y > point.y) {
      minPoint.y = point.y
    }

    if (maxPoint.x < point.x) {
      maxPoint.x = point.x
    }
    if (maxPoint.y < point.y) {
      maxPoint.y = point.y
    }
  }

  let style = {
    x: minPoint.x,
    y: minPoint.y,
    width: maxPoint.x - minPoint.x,
    height: maxPoint.y - minPoint.y
  }

  expandEdge(style, POINT_CUT_DISTANCE)
  points = _.map(points, function(point) {
    return coordTransform(point)
  })
  doTranslatePoints(points, {
    x: -style.x,
    y: -style.y
  })

  return {
    style: style,
    attr: {
      points: points
    }
  }
}

/**
 * 给连接点padding
 * @param  {[type]} style [description]
 * @param  {[type]} size  [description]
 * @return {[type]}       [description]
 */
function expandEdge(style, size) {
  style.x -= size;
  style.y -= size;
  style.width += 2 * size;
  style.height += 2 * size;
}

/**
 * 对象坐标转数组类型
 * @param  {[type]} point [description]
 * @return {[type]}       [description]
 */
function coordTransform(point) {
  return [point.x, point.y]
}

/**
 * 计算偏移后坐标
 * @param  {[type]} points [description]
 * @param  {[type]} offset [description]
 * @return {[type]}        [description]
 */
function doTranslatePoints(points, offset) {
  _.map(points, function(point) {
    point[0] += offset.x;
    point[1] += offset.y;
  })
}

/**
 * point 规定格式 {x:0,y:0}
 * direction 规定格式 's/w/e/n'
 * 返回edge的一些option:
 * edge.el 本身的props，还有重新计算过的始终点坐标（基于新的edge坐标系）
 * 
 * @param  {[type]} opts
 * {
 *   src: { point: {x,y} , direction:''},
 *   dst: { point: {x,y} , direction:''}
 * }
 * @return {[type]}      [description]
 */
function calcStraightLinePath(opts) {

  let source = opts.src;
  let target = opts.dst;

  let sourcePoint = source.point;
  let targetPoint = target.point;

  let style = {
    x: min(sourcePoint.x, targetPoint.x),
    y: min(sourcePoint.y, targetPoint.y),
    width: abs(targetPoint.x - sourcePoint.x),
    height: abs(targetPoint.y - sourcePoint.y)
  };

  let points = [];
  points.push(coordTransform(sourcePoint))
  points.push(coordTransform(targetPoint));

  expandEdge(style, POINT_CUT_DISTANCE)
  doTranslatePoints(points, {
    x: -style.x,
    y: -style.y
  })

  return {
    style: style,
    attr: {
      points: points
    }
  }
}

function endDirection(startX, startY, endX, endY) {
  if (startX === endX) {
    if (endY > startY) {
      return 'n';
    } else {
      return 's';
    }
  } else {
    if (endX > startX) {
      return 'w';
    } else {
      return 'e';
    }
  }
}